# -*- coding: utf-8 -*-
"""
@File    : justdoo_code
@Author  : Lucas
@Date    : 2024-08-15 21:59:16
@Description: 动态执行代码
"""

import json
import base64
import logging
from odoo.tools import date_utils
from odoo.tools.safe_eval import safe_eval
from odoo import api, fields, models, tools
from odoo.tools.float_utils import float_compare
from odoo.exceptions import UserError, ValidationError


_logger = logging.getLogger(__name__)

_code_action_logger = _logger.getChild("code_action_safe_eval")


class CodeLoggerProxy:
    """ Proxy of the `_logger` element in order to be used in Dynamic Python Code.
    We purposefully restrict its method as it will be executed in `safe_eval`.
    """
    @staticmethod
    def log(level, message, *args, stack_info=False, exc_info=False):
        _code_action_logger.log(level, message, *args, stack_info=stack_info, exc_info=exc_info)

    @staticmethod
    def info(message, *args, stack_info=False, exc_info=False):
        _code_action_logger.info(message, *args, stack_info=stack_info, exc_info=exc_info)

    @staticmethod
    def warning(message, *args, stack_info=False, exc_info=False):
        _code_action_logger.warning(message, *args, stack_info=stack_info, exc_info=exc_info)

    @staticmethod
    def error(message, *args, stack_info=False, exc_info=False):
        _code_action_logger.error(message, *args, stack_info=stack_info, exc_info=exc_info)

    @staticmethod
    def exception(message, *args, stack_info=False, exc_info=True):
        _code_action_logger.exception(message, *args, stack_info=stack_info, exc_info=exc_info)


class JustdooDynamicCode(models.Model):
    _name = 'justdoo.dynamic.code'
    _description = '动态执行代码'

    DEFAULT_PYTHON_CODE = """# Available variables:
    #  - env: environment on which the action is triggered
    #  - model: model of the record on which the action is triggered; is a void recordset
    #  - record: record on which the action is triggered; may be void
    #  - records: recordset of all records on which the action is triggered in multi-mode; may be void
    #  - time, datetime, dateutil, timezone: useful Python libraries
    #  - float_compare: utility function to compare floats based on specific precision
    #  - _logger: _logger.info(message): logger to emit messages in server logs
    # To return an action, assign: action = {...}\n\n\n\n"""

    name = fields.Char('说明', required=True)
    res_model = fields.Char('资源模型', required=True)
    res_method = fields.Char(string='资源方法')
    res_args = fields.Char('位置参数', help='未指定默认值的参数，默认[]')
    res_kwargs = fields.Char('字典参数', help='指定默认值的参数，默认{}')
    res_ids = fields.Char(string='资源ID', help='待调用记录ID，多个ID用逗号隔开')
    dynamic_code = fields.Text(string='动态代码', default=DEFAULT_PYTHON_CODE)
    company_id = fields.Many2one('res.company', '公司', default=lambda self: self.env.company)
    execute_type = fields.Selection([('auto', '自动'), ('manual', '手工')], '执行方式', default='auto')

    def action_execute_code(self):
        temp_env = self.env
        for operation in self:
            if not operation.res_model:
                _logger.error('未指定资源模型')
                continue
            if operation.res_method:
                res_args = []
                if operation.res_args and operation.res_args.strip():
                    res_args = json.loads(operation.res_args.strip())
                res_kwargs = {}
                if operation.res_kwargs and operation.res_kwargs.strip():
                    res_kwargs = json.loads(operation.res_kwargs.strip())
                try:
                    api.call_kw(self.env[operation.res_model], operation.res_method, res_args, res_kwargs)
                except UserError as ue:
                    _logger.error(ue.args[0])
                except ValidationError as ve:
                    _logger.error(ve.args[0])
                except Exception as ept:
                    _logger.error(ept.args[0])
                continue
            # 动态代码执行
            if not operation.dynamic_code:
                continue
            record, records = False, False
            temp_model = self.env[operation.res_model]
            if operation.res_ids:
                res_id_list = [int(res_id) for res_id in operation.res_ids.split(',')]
                if len(res_id_list) == 0:
                    record = temp_model.browse(res_id_list[0])
                else:
                    records = temp_model.browse(res_id_list)
            eval_context = {
                'time': tools.safe_eval.time, 'datetime': tools.safe_eval.datetime,
                'dateutil': tools.safe_eval.dateutil, 'float_compare': float_compare,
                'b64encode': base64.b64encode, 'b64decode': base64.b64decode, 'UserError': UserError,
                'env': temp_env, 'model': temp_model, 'record': record, 'records': records, '_logger': CodeLoggerProxy
            }
            try:
                safe_eval(operation.dynamic_code.strip(), eval_context, mode="exec", nocopy=True)
            except Exception as error:
                _logger.error('Error：%s' % error)

    @api.model
    def execute_dynamic_code(self):
        """ 自动执行动态代码方法
        """
        enable_cron = tools.config.get('enable_cron', False)
        if not enable_cron:
            return True
        param_sudo = self.env['justdoo.system.param'].sudo()
        dynamic_code_sudo = self.env['justdoo.dynamic.code'].sudo()
        dynamic_code_active = param_sudo.get_param('justdoo.dynamic.code.active', '0') == '1'
        if dynamic_code_active:
            active_time = param_sudo.get_param('justdoo.dynamic.code.active.time', '')
            if not active_time:
                temp_active_time = fields.Datetime.to_string(fields.Datetime.now())
                param_sudo.set_param('justdoo.dynamic.code.active.time', temp_active_time)
                return True
            temp_active_time = fields.Datetime.from_string(active_time)
            if temp_active_time + date_utils.get_timedelta(2, 'hour') <= fields.Datetime.now():
                param_sudo.set_param('justdoo.dynamic.code.active', '0')
                param_sudo.set_param('justdoo.dynamic.code.active.time', '')
            return True
        param_sudo.set_param('justdoo.dynamic.code.active', '1')
        param_sudo.set_param('justdoo.dynamic.code.active.time', fields.Datetime.to_string(fields.Datetime.now()))
        _logger.info('动态代码自动执行任务执行开始.............................')
        while True:
            active_time = param_sudo.get_param('justdoo.dynamic.code.active.time', '')
            if not active_time:
                active_time = fields.Datetime.to_string(fields.Datetime.now())
                param_sudo.set_param('justdoo.dynamic.code.active.time', active_time)
            temp_active_time = fields.Datetime.from_string(active_time)
            if temp_active_time + date_utils.get_timedelta(2, 'hour') <= fields.Datetime.now():
                param_sudo.set_param('justdoo.dynamic.code.active', '0')
                param_sudo.set_param('justdoo.dynamic.code.active.time', '')
                _logger.info('动态代码自动执行任务执行结束：上次任务执行超时.............................')
                break
            code_domain = [('execute_type', '=', 'auto')]
            dynamic_code_list = dynamic_code_sudo.search(code_domain, order='id asc', limit=20)
            if not dynamic_code_list or len(dynamic_code_list) <= 0:
                param_sudo.set_param('justdoo.dynamic.code.active', '0')
                param_sudo.set_param('justdoo.dynamic.code.active.time', '')
                break
            dynamic_code_list.action_execute_code()
            self.env.cr.commit()
        _logger.info('动态代码自动执行任务执行结束.............................')
        return True










